<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Promise</title>
</head>
<body>
<script type="text/javascript">
    {
        /**
         * Promise的含义
         * 简单说就是一个容器，里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说，Promise是一个对象，从它可以获取异步操作的消息。Promise提供统一的API，各种异步操作都可以用同样的方法进行处理
         */
        /*Promise对象有以下两个特点
        * 1、对象的状态不受外界影响：Promise对象代表一个异步操作，有三种状态：Pending(进行中)、Resolved(已完成，又称Fulfilled)和Rejected(已失败)。
        * 只有异步操作的结果，可以决定当前是哪一种状态，任何其他操作都无法改变这个状态。这也是Promise这个名字的由来，它的英语意思就是“承诺”，表示其他手段无法改变
        * 2、一旦状态改变，就不会再变，任何时候都可以得到这个结果。
        * Promise对象的状态改变，只有两种可能：从Pending变为Resolved和从Pending变为Rejected。
        * 只要这两种情况发生，状态就凝固了，不会再变了，会一直保持这个结果。
        * 就算改变已经发生了，你再对Promise对象添加回调函数，也会立即得到这个结果。
        * 这与事件(Event)完全不同，事件的特点是，如果你错过了它，再去监听，是得不到结果的*/

        /*有了Promise对象，就可以将异步操作以同步操作的流程表达出来，避免了层层嵌套的回调函数。此外，Promise对象提供统一的接口，使得控制异步操作更加容易*/

        /*Promise也有一些缺点。
        首先，无法取消Promise，一旦新建它就会立即执行，无法中途取消。
        其次，如果不设置回调函数，Promise内部抛出的错误，不会反应到外部。
        第三，当处于Pending状态时，无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。*/

        /*如果某些事件不断地反复发生，一般来说，使用stream模式是比部署Promise更好的选择*/
    }
    {
        /**
         * 基本用法
         * Promise对象是一个构造函数，用来生成Promise实例
         */
        /*下面代码创造了一个Promise实例*/
        /*const promise = new Promise(function (resolve, reject) {
            // ... some code

            if (/!* 异步操作成功 *!/) {
                resolve(value);
            } else {
                reject(new Error());
            }
        });*/

        /*Promise构造函数接受一个函数作为参数，该函数的两个参数分别是resolve和reject。它们是两个函数，由JavaScript引擎提供，不用自己部署*/

        /*resolve函数的作用是，将Promise对象的状态从“未完成”变为“成功”(即从Pending变为Resolved)，在异步操作成功时调用，并将异步操作的结果，作为参数传递出去；
        reject函数的作用是，将Promise对象的状态从“未完成”变为“失败”(即从Pending变为Rejected)，在异步操作失败时调用，并将异步操作报出的错误，作为参数传递出去*/

        /*Promise实例生成以后，可以用then方法分别指定Resolved状态和Reject状态的回调函数*/

        /*then方法可以接受两个回调函数作为参数。
        第一个回调函数是Promise对象的状态变为Resolved时调用，
        第二个回调函数是Promise对象的状态变为Reject时调用。
        其中，第二个函数是可选的，不一定要提供。这两个函数都接受Promise对象传出的值作为参数*/

        /*下面是一个Promise对象的简单例子*/
        function timeout(ms) {
            return new Promise((resolve, reject) => {
                setTimeout(resolve, ms, 'done');
            });
        }

        timeout(100).then((value) => {
            console.log(value); // done
        });
        /*上面代码中，timeout方法返回一个Promise实例，表示一段时间以后才会发生的结果。过了指定的时间(ms参数)以后，Promise实例的状态变为Resolved，就会触发then方法绑定的回调函数*/

        let promise = new Promise(function (resolve, reject) {
            console.log('Promise');
            resolve();
        });
        promise.then(function () {
            console.log('Resolved.');
        });
        console.log('Hi!'); // Promise  Hi!  Resolved.
        /*上面代码中，Promise新建后立即执行，所以首先输出的是“Promise”。
        然后，then方法指定的回调函数，将在当前脚本所有同步任务执行完才会执行，所以“Resolved”最后输出*/

        /*下面是异步加载图片的例子*/
        function loadImageAsync(url) {
            return new Promise(function (resolve, reject) {
                const image = new Image();
                image.onload = function () {
                    resolve(image);
                };
                image.onerror = function () {
                    reject(new Error('Could not load image at ' + url));
                };
                image.src = url;
            });
        }

        // loadImageAsync("https://www.baidu.com");

        /*下面是一个用Promise对象实现的Ajax操作的例子*/
        const getJSON = function (url) {
            return new Promise(function (resolve, reject) {
                const client = new XMLHttpRequest();
                client.open("GET", url);
                client.onreadystatechange = handler;
                client.responseType = "json";
                client.setRequestHeader("Accept", "application/json");
                client.send();

                function handler() {
                    if (this.readyState !== 4) {
                        return;
                    }
                    if (this.status === 200) {
                        resolve(this.response);
                    } else {
                        reject(new Error(this.statusText));
                    }
                }
            });
        };
        /*getJSON("/posts.json").then(function(json) {
            console.log('Contents: ' + json);
        }, function(error) {
            console.error('出错了', error);
        });*/

        const p1 = new Promise(function (resolve, reject) {
            setTimeout(() => reject(new Error('fail')), 3000)
        });

        const p2 = new Promise(function (resolve, reject) {
            setTimeout(() => resolve(p1), 1000)
        });

        p2.then(result => console.log(result)).catch(error => console.log(error)); // Error: fail
        /*上面代码中，p1是一个Promise，3秒之后变为rejected。p2的状态在1秒之后改变，resolve方法返回的是p1。
        此时，由于p2返回的是另一个Promise，所以后面的then语句都变成针对后者(p1)。
        又过了2秒，p1变为rejected，导致触发catch方法指定的回调函数。*/
    }
    {
        /**
         * Promise.prototype.then()
         */
        const promise = new Promise(function (resolve, reject) {
            throw new Error('test');
        });
        promise.catch(function (error) {
            console.log(error); // Error: test
        });
        /*等同写法1*/
        /*const promise = new Promise(function (resolve, reject) {
            try {
                throw new Error('test');
            } catch (e) {
                reject(e);
            }
        });
        promise.catch(function(error) {
            console.log(error);
        });*/
        /*等同写法2*/
        /*const promise = new Promise(function(resolve, reject) {
            reject(new Error('test'));
        });
        promise.catch(function(error) {
            console.log(error);
        });*/

        /*如果Promise状态已经变成Resolved，再抛出错误是无效的*/
        const p = new Promise(function (resolve, reject) {
            resolve('ok');
            throw new Error('test');
        });
        p.then(function (value) {
            console.log(value)
        }).catch(function (error) {
            console.log(error)
        }); // ok
        /*上面代码中，Promise在resolve语句后面，再抛出错误，不会被捕获，等于没有抛出。*/

        const someAsyncThing = function () {
            return new Promise(function (resolve, reject) {
                // 下面一行会报错，因为x没有声明
                // resolve(x + 2);
            });
        };
        someAsyncThing().then(function() {
            console.log('everything is great'); // Uncaught (in promise) ReferenceError: x is not defined
        });

        const promise1 = new Promise(function (resolve, reject) {
            resolve("ok");
            setTimeout(function () {
                // throw new Error('test')
            }, 0)
        });
        promise1.then(function(value) { console.log(value) }); // ok  Uncaught Error: test
        /*上面代码中，Promise指定在下一轮“事件循环”再抛出错误，结果由于没有指定使用try...catch语句，就冒泡到最外层，成了未捕获的错误。因为此时，Promise的函数体已经运行结束了，所以这个错误是在Promise函数体外抛出的。*/
    }
    {
        /**
         * Promise.all()：用于将多个Promise实例，包装成一个新的Promise实例
         */
        // const p = Promise.all([p1, p2, p3]);
        /*Promise.all方法的参数可以不是数组，但必须具有Iterator接口，且返回的每个成员都是Promise实例。*/
    }
    {
        /**
         * Promise.race()：将多个Promise实例，包装成一个新的Promise实例
         */
        /*下面是一个例子，如果指定时间内没有获得结果，就将Promise的状态变为reject，否则变为resolve*/
        const p = Promise.race([
            fetch('/resource-that-may-take-a-while'),
            new Promise(function (resolve, reject) {
                setTimeout(() => reject(new Error('request timeout')), 5000)
            })
        ]);
        p.then(response => console.log(response)); //Response {type: "basic", url: "http://localhost:63342/resource-that-may-take-a-while", redirected: false, status: 404, ok: false, …}
        p.catch(error => console.log(error));
        /*上面代码中，如果5秒之内fetch方法无法返回结果，变量p的状态就会变为rejected，从而触发catch方法指定的回调函数*/
    }
    {
        /**
         * Promise.resolve()：有时需要将现有对象转为Promise对象，Promise.resolve方法就起到这个作用
         * 参数分成四种情况
         * 1、参数是一个Promise实例
         * 2、参数是一个thenable对象
         * 3、参数不是具有then方法的对象，或根本就不是对象
         * 4、不带有任何参数
         */
        /*1、参数是一个Promise实例：如果参数是Promise实例，那么Promise.resolve将不做任何修改、原封不动地返回这个实例*/

        /*2、参数是一个thenable对象：thenable对象指的是具有then方法的对象，比如下面这个对象*/
        let thenable = {
            then: function (resolve, reject) {
                resolve(42);
            }
        };
        let p1 = Promise.resolve(thenable);
        p1.then(function (value) {
            console.log(value);  // 42
        });
        /*上面代码中，thenable对象的then方法执行后，对象p1的状态就变为resolved，从而立即执行最后那个then方法指定的回调函数，输出42*/

        /*3、参数不是具有then方法的对象，或根本就不是对象：如果参数是一个原始值，或者是一个不具有then方法的对象，则Promise.resolve方法返回一个新的Promise对象，状态为Resolved*/
        const p = Promise.resolve('Hello');

        p.then(function (s) {
            console.log(s); // Hello
        });

        /*4、不带有任何参数：直接返回一个Resolved状态的Promise对象*/
        setTimeout(function () {
            console.log('three');
        }, 0);

        Promise.resolve().then(function () {
            console.log('two');
        });

        console.log('one'); // one two three
        /*上面代码中，setTimeout(fn, 0)在下一轮“事件循环”开始时执行，Promise.resolve()在本轮“事件循环”结束时执行，console.log(’one‘)则是立即执行，因此最先输出。*/
    }
    {
        /**
         * Generator函数与Promise的结合
         * 使用Generator函数管理流程，遇到异步操作的时候，通常返回一个Promise对象
         */
        function getFoo () {
            return new Promise(function (resolve, reject){
                resolve('foo');
            });
        }

        const g = function* () {
            try {
                const foo = yield getFoo();
                console.log(foo);
            } catch (e) {
                console.log(e);
            }
        };

        function run (generator) {
            const it = generator();

            function go(result) {
                if (result.done) return result.value;

                return result.value.then(function (value) {
                    return go(it.next(value));
                }, function (error) {
                    return go(it.throw(error));
                });
            }

            go(it.next());
        }

        run(g); // foo
        /*上面代码的Generator函数g之中，有一个异步操作getFoo，它返回的就是一个Promise对象。函数run用来处理这个Promise对象，并调用下一个next方法*/
    }
</script>
</body>
</html>